#include <dim-sum/beehive.h>
#include <dim-sum/blk_infrast.h>
#include <dim-sum/err.h>
#include <dim-sum/fnode.h>
#include <dim-sum/magic.h>
#include <dim-sum/mount.h>
#include <dim-sum/pagemap.h>
#include <dim-sum/sched.h>
#include <dim-sum/smp_rwlock.h>
#include <dim-sum/stacktrace.h>
#include <dim-sum/syscall.h>
#include <dim-sum/fs.h>

struct cpio_newc_header {
	char c_magic[6];
	char c_ino[8];
	char c_mode[8];
	char c_uid[8];
	char c_gid[8];
	char c_nlink[8];
	char c_mtime[8];
	char c_filesize[8];
	char c_devmajor[8];
	char c_devminor[8];
	char c_rdevmajor[8];
	char c_rdevminor[8];
	char c_namesize[8];
	char c_check[8];
} __attribute__((packed));

struct cpio_node {
	int name_len;
	umode_t mode;
	size_t file_size;
	unsigned long cpio_off;
	char name[NAME_MAX];
    char path[NAME_MAX];
};

/**
 * cpio image info from linker
 */
extern char _cpio_start[];
extern char _cpio_end[];

static unsigned long cpio_image_sz = 0;

static unsigned long cpio_blk_read(void *buf, long off, unsigned long len)
{
    memcpy((void *)buf, (const void *)(_cpio_start + off), len);
	return len;
}

static unsigned long cpio_blk_get_capacity(void)
{
    return cpio_image_sz;
}

static void cpio_blk_init(void)
{
    cpio_image_sz = (unsigned long)(_cpio_end - _cpio_start);
}

__maybe_unused static int cpio_check_valid(void)
{
	struct cpio_newc_header header;
	unsigned long rd;

	if(cpio_blk_get_capacity() <= sizeof(struct cpio_newc_header))
		return -1;

	rd = cpio_blk_read((unsigned char *)(&header), 0, sizeof(struct cpio_newc_header));
	if(rd != sizeof(struct cpio_newc_header))
		return -1;

	if(strncmp((const char *)header.c_magic, "070701", 6) != 0)
		return -1;

	return 0;
}

static bool get_next_token(const char * path, const char * prefix, char * result)
{
	const char * p, * q;
	int l;

	if(!path || !prefix || !result)
		return false;

	if(*path == '/')
		path++;

	if(*prefix == '/')
		prefix++;

	l = strlen(prefix);
	if(strncmp(path, prefix, l) != 0)
		return false;

	p = &path[l];
	if(*p == '\0')
		return false;
	if(*p == '/')
		p++;
	if(*p == '\0')
		return false;

	q = strchr(p, '/');
	if(q)
	{
		if (*(q + 1) != '\0')
			return false;
		l = q - p;
	}
	else
	{
		l = strlen(p);
	}
	memcpy(result, p, l);
	result[l] = '\0';

	return true;
}

static bool check_path(const char * path, const char * prefix, const char * name)
{
	int l;

	if(!path || !prefix || !name)
		return false;

	if(path[0] == '/')
		path++;

	if(prefix[0] == '/')
		prefix++;

	l = strlen(prefix);
	if(l && (strncmp(path, prefix, l) != 0))
		return false;

	path += l;

	if(path[0] == '/')
		path++;

	if(strcmp(path, name) != 0)
		return false;

	return true;
}

static unsigned long cpio_read_raw(struct cpio_node *node, long off, const void * buf, unsigned long len)
{
	unsigned long toff;
	unsigned long sz = 0;

	/**
	 * 普通文件和链接文件可以被读取
	 */
	if(!S_ISREG(node->mode) && !S_ISLNK(node->mode))
		return 0;

	if(off >= node->file_size)
		return 0;

	sz = len;
	if((node->file_size - off) < sz)
		sz = node->file_size - off;

	toff = node->cpio_off;
	sz = cpio_blk_read((char *)buf, (toff + off), sz);

	return sz;
}

static unsigned long cpio_get_off(struct cpio_node *node, long off)
{
	unsigned long toff;

	if(off >= node->file_size)
		return 0;

	toff = node->cpio_off;

    return (unsigned long)(_cpio_start + (toff + off));
}

static int cpio_readdir_raw(char *parent_path, long off, struct cpio_node *node)
{
	struct cpio_newc_header header;
	unsigned int size, name_size, mode;
	unsigned long toff = 0, rd;
	char buf[9];
	int i = 0;

    char *name = node->name;
    char *path = node->path;

	while(1) {
        memset(name, 0, NAME_MAX);
        memset(path, 0, NAME_MAX);
        
		rd = cpio_blk_read((unsigned char *)&header, toff, sizeof(struct cpio_newc_header));
		if(rd != sizeof(struct cpio_newc_header))
			return -1;

		if(strncmp((const char *)&header.c_magic, "070701", 6) != 0)
			return -1;

		buf[8] = '\0';

		memcpy(buf, &header.c_filesize, 8);
		size = simple_strtoull((const char *)buf, NULL, 16);

		memcpy(buf, &header.c_namesize, 8);
		name_size = simple_strtoull((const char *)buf, NULL, 16);

		memcpy(buf, &header.c_mode, 8);
		mode = simple_strtoull((const char *)buf, NULL, 16);

		rd = cpio_blk_read( (unsigned char *)path, toff + sizeof(struct cpio_newc_header), name_size);
		if(!rd)
			return -1;

		if((size == 0) && (mode == 0) && (name_size == 11) && (strncmp(path, "TRAILER!!!", 10) == 0))
			return -1;

		toff += sizeof(struct cpio_newc_header);
		toff += (((name_size + 1) & ~3) + 2) + size;
		toff = (toff + 3) & ~3;

		if(path[0] == '.')
			continue;

		if(!get_next_token(path, parent_path, name))
			continue;

		if(i++ == off) {
			toff = 0;
			break;
		}
	}

	return 0;
}

static int cpio_lookup_raw(char *parent_path, const char * name, struct cpio_node *node)
{
	struct cpio_newc_header header;
	unsigned long off = 0, rd;
	unsigned int size, name_size, mode, mtime;
	unsigned char buf[9];

    char *path = node->path;

	while(1) {
        memset(path, 0, NAME_MAX);
        
		rd = cpio_blk_read((unsigned char *)&header, off, sizeof(struct cpio_newc_header));
		if(rd != sizeof(struct cpio_newc_header))
			return -1;

		if(strncmp((const char *)header.c_magic, "070701", 6) != 0)
			return -1;

		buf[8] = '\0';

		memcpy(buf, &header.c_filesize, 8);
		size = simple_strtoull((const char *)buf, NULL, 16);

		memcpy(buf, &header.c_namesize, 8);
		name_size = simple_strtoull((const char *)buf, NULL, 16);

		memcpy(buf, &header.c_mode, 8);
		mode = simple_strtoull((const char *)buf, NULL, 16);

		memcpy(buf, &header.c_mtime, 8);
		mtime = simple_strtoull((const char *)buf, NULL, 16);
        (void)mtime;
		rd = cpio_blk_read((unsigned char *)path, off + sizeof(struct cpio_newc_header), name_size);
		if(!rd)
			return -1;

		if((size == 0) && (mode == 0) && (name_size == 11) && (strncmp(path, "TRAILER!!!", 10) == 0))
			return -1;

		if((path[0] != '.') && check_path(path, parent_path, name))
			break;

		off += sizeof(struct cpio_newc_header);
		off += (((name_size + 1) & ~3) + 2) + size;
		off = (off + 3) & ~0x3;
	}
	
	node->mode = mode;
	node->file_size = size;
	node->name_len = name_size;
	
	off += sizeof(struct cpio_newc_header);
	off += (((name_size + 1) & ~3) + 2);
	node->cpio_off = off;

	return 0;
}

static int extra_dir(char *parent_path, struct cpio_node *node)
{
    long de_off;

    de_off = 0;

    while (1)
    {   
        if (cpio_readdir_raw(parent_path, de_off, node)) {
            return -1;
        }

        if (cpio_lookup_raw(parent_path, node->name, node)) {
            pr_err("cpio: lookup node path:%s off:%d failed!\n", parent_path, de_off);
            return -1;
        }

        /**
         * 找到节点，根据节点创建文件，目录，符号链接。
         */
        switch (node->mode & S_IFMT) {
        default:
            pr_err("(%s): unknown mode %o node: %p\n",
                node->name, node->mode, node);
            break;
        case S_IFREG:
            {
                // pr_info("create and write file %s, size %d\n", node->name, node->file_size);

                char *new_path;
                int fd;

                size_t n = strlen(parent_path);
                /**
                 * 分配节点的路径长度: 目录路径长度 + '/' + 节点名字长度 + '\0'
                 */
                new_path = kmalloc(n + 1 + node->name_len + 1, PAF_KERNEL | __PAF_ZERO);
                if (!new_path) {
                    pr_err("kmalloc no enough memory for cpio!\n");
                    return -1;
                }

                strcpy(new_path, parent_path);
                if (new_path[n - 1] != '/')
                    strcat(new_path, "/");
                strcat(new_path, node->name);

                fd = sys_open(new_path, O_CREAT | O_WRONLY, node->mode);
                if (fd < 0) {
                    pr_err("can not create file %s!\n", new_path);                    
                    kfree(new_path);
                    return -1;
                }

                if (sys_write(fd, (const char *)cpio_get_off(node, 0), node->file_size) != node->file_size) {
                    pr_err("can not extra file %s!\n", new_path);                    
                    sys_close(fd);
                    kfree(new_path);
                    return -1;
                }

                sys_close(fd);
            }
            break;
        case S_IFDIR:
            {
                /**
                 * 构建新路径，并递归查找
                 */
                char *new_path;
                struct cpio_node *new_node;

                size_t n = strlen(parent_path);
                /**
                 * 分配节点的路径长度: 目录路径长度 + '/' + 节点名字长度 + '\0'
                 */
                new_path = kmalloc(n + 1 + node->name_len + 1, PAF_KERNEL | __PAF_ZERO);
                if (!new_path) {
                    pr_err("kmalloc no enough memory for cpio!\n");
                    return -1;
                }

                new_node = kmalloc(sizeof(struct cpio_node), PAF_KERNEL | __PAF_ZERO);
                if (!new_node) {
                    pr_err("kmalloc no enough memory for cpio!\n");
                    kfree(new_path);
                    return -1;
                }

                strcpy(new_path, parent_path);
                if (new_path[n - 1] != '/')
                    strcat(new_path, "/");
                strcat(new_path, node->name);

                /* create dir */
                if (sys_mkdir(new_path, node->mode) < 0) {
                    pr_err("can not create dir %s!\n", new_path);                    
                    kfree(new_node);
                    kfree(new_path);
                    return -1;
                }

                extra_dir(new_path, new_node);
                
                kfree(new_node);
                kfree(new_path);
            }
            break;
        case S_IFLNK:
            {
                // pr_info("create symble %s, size %d\n", node->name, node->file_size);
                char *sym;
                char *new_path;
                char *p;
                size_t n = strlen(parent_path);

                sym = kmalloc(NAME_MAX, PAF_KERNEL | __PAF_ZERO);
                if (!sym) {
                    pr_err("kmalloc no enough memory for cpio!\n");
                    break;
                }

                /**
                 * 分配节点的路径长度: 目录路径长度 + '/' + 节点名字长度 + '\0'
                 */
                new_path = kmalloc(n + 1 + node->name_len + 1, PAF_KERNEL | __PAF_ZERO);
                if (!new_path) {
                    pr_err("kmalloc no enough memory for cpio!\n");
                    kfree(sym);
                    break;
                }

                /**
                 * build full path
                 */
                strcpy(new_path, parent_path);
                if (new_path[n - 1] != '/')
                    strcat(new_path, "/");
                strcat(new_path, node->name);

                strcpy(sym, parent_path);
                p = sym + n;
                if (sym[n - 1] != '/') {
                    strcat(sym, "/");
                    p++;
                }

                /**
                 * get symlink
                 */
                if (!cpio_read_raw(node, 0, p, node->file_size)) {
                    pr_err("read symlink %s failed!\n", new_path);
                    kfree(new_path);
                    kfree(sym);
                    break;
                }

                // pr_info("symlink %s -> %s\n", new_path, sym);
                if (sys_symlink(sym, new_path) != 0) {
                    pr_err("create symlink %s -> %s failed!\n", new_path, sym);
                    kfree(new_path);
                    kfree(sym);
                    break;
                }

                kfree(new_path);
                kfree(sym);
            }
            break;
        }
        /**
         * 查找下一个文件
         */
        de_off++;
    }
    return 0;
}

void __init extra_cpio_rootfs(void)
{
    struct cpio_node *node;
    char parent_path[8] = {0};
    
    cpio_blk_init();
    
    node = kmalloc(sizeof(struct cpio_node), PAF_KERNEL | __PAF_ZERO);
    if (!node) {
        pr_err("kmalloc no enough memory for cpio!\n");
        return;
    }
    strcpy(parent_path, "/");
    extra_dir(parent_path, node);
    kfree(node);
}
